#include "post.h"
#include <iostream>
#include <string>
#include <vector>
#include <cctype>


//Funcao que verifica a existencia de um comando inicial como primeiro comando
//e verifica a existencia do atributo vai_para, tornando-o um comando inicial valido
int post::possui_inicial()
{
	XMLNode comandos;
	comandos = raiz.getChildNode("maquina_post");
	comandos = comandos.getChildNode("programa");
        //
        int maxCmds = comandos.nChildNode("comando");
	
	if(comandos.getChildNode("comando", 0).getAttribute("inicio"))
	{
            if(comandos.getChildNode("comando", 0).getAttribute("vai_para"))
            {
                //verifica se o dado fornecido no parametro vai_para é um numero
                std::string aux;
                aux = comandos.getChildNode("comando", 0).getAttribute("vai_para");
                for(int i = 0; i < aux.length(); i++)
                {
                    if(!isdigit(aux.at(i)))
                    {
                        std::cout << "Atributo vai_para deve ser um inteiro" << std::endl;
                        return FALSE;
                    }
                }
                //verifica se o comando vai_para aponta para uma posicao valida
                if (atoi(comandos.getChildNode("comando", 0).getAttribute("vai_para")) < 0
                || atoi(comandos.getChildNode("comando", 0).getAttribute("vai_para")) > maxCmds)
                {
                    std::cout << "Atributo vai_para do comando inicial aponta para um comando inexistente" << std::endl;
                    return FALSE;
                }
                //ja atualiza o comando atual
                com_atual = atoi(comandos.getChildNode("comando").getAttribute("vai_para"));
                return TRUE;
            }else
                std::cout << "O formato correto de um comando de inicio e: <comando inicio=\"1\" vai_para=\"id_prox_comando\"/> " << std::endl;
        }else
            std::cout << "O programa precisa possuir um comando de inicio como primeiro comando." << std::endl;
    return FALSE;
}

void post::imprimeX()
{
    for(int i=0; i<X.size(); i++)
    {
        std::cout << X[i];
    }
    std::cout << std::endl;
}

//coloca entrada na variavel X verificando se a entrada esta em conformidade com o alfabeto
//e ja atualizando a variavel com_atual para a primeira instrucao do programa
int post::recebe_entrada(int x){
    std::string input;
    XMLNode alfabeto;
    alfabeto = raiz.getChildNode("maquina_post");
    alfabeto = alfabeto.getChildNode("alfabeto");
    input = raiz.getChildNode("entrada", x).getText();
    int simbolo_valido = 0;
    //2 lacos: um percorre cada caractere da entrada, e o outro verifica se o simbolo se encontra no alfabeto
    for(int y=0; y<input.size(); y++)
    {
        for(int i=0; i<alfabeto.nChildNode("simbolo"); i++)
        {
            if(input[y] == *alfabeto.getChildNode(i).getText())
            {
                simbolo_valido = 1;
                break;
            }
        }
        if(simbolo_valido == 0)
            return FALSE;
        X.push_back(input[y]);
        simbolo_valido = 0;
    }
    return TRUE;
}

int post::verifica_programa()
{
    XMLNode comandos = raiz.getChildNode("maquina_post");
    comandos = comandos.getChildNode("programa");
    
    //verifica a existencia do inicio de um programa e se ele e o primeiro comando
    //ja colocando com_atual em sua devida posicao
    if(!possui_inicial())
        return FALSE;
    
//variavel que guardara o numero de comandos, ajudando a verificar se os demais comandos estao corretos
    int maxCmds = comandos.nChildNode("comando");
    
    //variaveis para verificacao de um comando de aceitacao ou rejeicao
    int possui_aceita = 0;
    int possui_rejeita = 0;

    //for que verificara  se os comandos possuem os atributos corretos e apontam para comandos validos
    for(int i=1; i<maxCmds; i++)
    {    
        
        //comando atrib deve conter atrib, vai_para
        if(comandos.getChildNode("comando", i).getAttribute("atrib")
           && comandos.getChildNode("comando", i).getAttribute("vai_para"))
        {
            
            //verifica se o dado fornecido no parametro vai_para é um numero
            std::string aux;
            aux = comandos.getChildNode("comando", i).getAttribute("vai_para");
            for(int j = 0; j < aux.length(); j++)
            {
                if(!isdigit(aux.at(j)))
                {
                    std::cout << "Atributo vai_para deve ser um inteiro" << std::endl;
                    return FALSE;
                }
            }
            
            //verifica se o comando vai_para aponta para uma posicao valida
            if (atoi(comandos.getChildNode("comando", i).getAttribute("vai_para")) < 0
                || atoi(comandos.getChildNode("comando", i).getAttribute("vai_para")) > maxCmds)
            {
                std::cout << "Atributo vai_para do comando " << i << " aponta para um comando inexistente" << std::endl;
                return FALSE;
            }
        }
        
        //comando ler deve conter ler, vai_para, senao
        else if(comandos.getChildNode("comando", i).getAttribute("ler")
           && comandos.getChildNode("comando", i).getAttribute("vai_para")
           && comandos.getChildNode("comando", i).getAttribute("senao"))
        {
            
            //verifica se o dado fornecido no parametro vai_para é um numero
            std::string aux;
            aux = comandos.getChildNode("comando", i).getAttribute("vai_para");
            for(int j = 0; j < aux.length(); j++)
            {
                if(!isdigit(aux.at(j)))
                {
                    std::cout << "Atributo vai_para deve ser um inteiro" << std::endl;
                    return FALSE;
                }
            }
            
            //verifica se o dado fornecido no parametro senao é um numero
            aux = comandos.getChildNode("comando", i).getAttribute("senao");
            for(int j = 0; j < aux.length(); j++)
            {
                if(!isdigit(aux.at(j)))
                {
                    std::cout << "Atributo senao deve ser um inteiro" << std::endl;
                    return FALSE;
                }
            }
            
            //verifica se o comando vai_para aponta para uma posicao valida
            if (atoi(comandos.getChildNode("comando", i).getAttribute("vai_para")) < 0
                || atoi(comandos.getChildNode("comando", i).getAttribute("vai_para")) > maxCmds)
            {
                std::cout << "Atributo vai_para do comando " << i << " aponta para um comando inexistente" << std::endl;
                return FALSE;
            }
            
            //verifica se o comando senao aponta para uma posicao valida
            if (atoi(comandos.getChildNode("comando", i).getAttribute("senao")) < 0
                || atoi(comandos.getChildNode("comando", i).getAttribute("senao")) > maxCmds)
            {
                std::cout << "Atributo senao do comando " << i << " aponta para um comando inexistente" << std::endl;
                return FALSE;
            }
        }
        
        //se for um comando rejeita, seta para 1 a variavel possui_rejeita
        else if (comandos.getChildNode("comando", i).getAttribute("rejeita"))
            possui_rejeita = 1;
        
        //se for um comando aceita, seta para 1 a variavel possui_aceita
        else if (comandos.getChildNode("comando", i).getAttribute("aceita"))
            possui_aceita = 1;
        
        else
        {
            std::cout << "O programa nao foi escrito corretamente." << std::endl;
            std::cout << "O ultimo comando valido foi: " << i << std::endl;
            //retorna falso caso o comando atual nao respeite as regras acima
            return FALSE;
        }
    }
    
    //verifica se a variavel possui_aceita foi incrementada, se nao retorna falso
    if (possui_aceita == 0)
    {
        std::cout << "O programa precisa ter um comando \"aceita\" " << std::endl;
        return FALSE;
    }
    
    //verifica se a variavel possui_rejeita foi incrementada, se nao retorna falso
    if (possui_rejeita == 0)
    {
        std::cout << "O programa precisa ter um comando \"rejeita\" " << std::endl;
        return FALSE;
    }
    
    return TRUE;
}

void post::executa_programa(int exec)
{
    std::string aux;           //variavel para auxiliar na conversao de tipos
    XMLNode comandos;          //no que ira conter como filhos todos os comandos do programa
    comandos = raiz.getChildNode("maquina_post");
    comandos = comandos.getChildNode("programa");
		
    //laco de execucao da maquina, para ao encontrar comandos de aceita ou rejeita
    while(1)
    {
        //verifica se a entrada foi aceita
        if(comandos.getChildNode("comando", com_atual).getAttribute("aceita"))
        {
            std::cout << "A entrada foi aceita pela maquina de post fornecida" << std::endl;
            break;
        }
            
        //verifica se a entrada foi rejeitada
        if(comandos.getChildNode("comando", com_atual).getAttribute("rejeita"))
        {
            std::cout << "A entrada foi rejeitada pela maquina de post fornecida" << std::endl;
            break;
        }
        
        //se o comando atual for uma atribuicao, coloca o simbolo lido no fim da variavel X
        //atualizando com_atual
        if(comandos.getChildNode("comando", com_atual).getAttribute("atrib"))
        {
            aux = comandos.getChildNode("comando", com_atual).getAttribute("atrib");
            if (exec == 1)
            {
                std::cout << "Estado da variavel X: ";
                imprimeX();
                std::cout << "Instrucao atual: atrib " << aux.at(0) << std::endl << std::endl;
                getchar();
            }
            X.push_back(aux.at(0));
            com_atual = atoi(comandos.getChildNode("comando", com_atual).getAttribute("vai_para"));
        
        //se for um comando de leitura, remove o simbolo lido do inicio da variavel X
        //atualizando com_atual
        }else if(comandos.getChildNode("comando", com_atual).getAttribute("ler"))
        {
            aux = comandos.getChildNode("comando", com_atual).getAttribute("ler");
            
            if (aux.at(0) == '@' && X.empty())
                com_atual = atoi(comandos.getChildNode("comando", com_atual).getAttribute("vai_para"));
                    
            else if (aux.at(0) == X[0])
            {
                if (exec == 1)
                {
                    std::cout << "Estado da variavel X: ";
                    imprimeX();
                    std::cout << "Instrucao atual: ler " << aux.at(0) << std::endl << std::endl;
                    getchar();
                }
                com_atual = atoi(comandos.getChildNode("comando", com_atual).getAttribute("vai_para"));
                std::vector<char>::iterator startIterator;
                startIterator = X.begin();
                X.erase(startIterator);
            }else
                com_atual = atoi(comandos.getChildNode("comando", com_atual).getAttribute("senao"));
        }
    }
}

//funcao principal de execucao da maquina
//executa todas as emtradas na dada maquina de post
void post::m_post(XMLNode *entrada)
{
    std::cout << std::endl << "***Iniciando execucao da maquina de post para as entradas fornecidas***" << std::endl << std::endl;
    this->raiz = *entrada;

    int exec = 0;              //execucao passo a passo (1) ou continua (0) padrao

    if (verifica_programa())
    {
        //este laco garantira que o programa executara para todas as entradas
        for(int i=0; i<entrada->nChildNode("entrada"); i++)
        {
            //exec e a opcao de cada entrada, se e execucao direta ou passo a passo
            if(entrada->getChildNode("entrada",i).getAttribute("passo"))
            {
                std::cout << std::endl << "Executando entrada " << i+1 << " no modo passo a passo." << std::endl;
                exec = 1;
            }else
            {
                std::cout << std::endl << "Executando entrada " << i+1 << " no modo continuo." << std::endl;
                exec = 0;
            }
            
            //coloca entrada atual no vetor
            if (!recebe_entrada(i))
                std::cout << "Entrada "<< i+1 << " invalida, possui um ou mais simbolos que nao estao presentes no alfabeto de entrada" << std::endl;
            else
            {
                std::cout << "Executando a maquina de post para a entrada: ";
                imprimeX();
                //chama a funcao de execucao para a entrada atual passando o modo de execucao
                executa_programa(exec);
            }
    
            X.clear();
            com_atual = atoi(entrada->getChildNode("maquina_post").getChildNode("programa").getChildNode("comando", 0).getAttribute("vai_para"));

            std::cout << std::endl << "Pressione qualquer tecla para continuar" << std::endl << std::endl;
            getchar();
        }
    }
    std::cout << std::endl << "***Execucao da maquina de post finalizada.***" << std::endl << std::endl;
}
